//***************************************************************
//
// Created BY: JESSE SAHOTA & Jared
// COURSE: CPSC 589
//
//***************************************************************
//
//Notes: Some ideas and code is referenced, taken and inspired by Steven Longay.
//
//Supervised by samavati
//****************************************************************
//***************************************************************
// LIBRARIES
//***************************************************************
#include "Plant.h"
#include <limits>
//***************************************************************
// CONSTANTS
//***************************************************************
const float LARGE_FLOAT = std::numeric_limits<float>::max();
const double SEGMENT_WIDTH = 1.0;

#define REG_ID 0
#define PINE_ID 1

#define SKELETON_ID 0
#define WIRE_FRAME_ID 1
#define SMOOTH_ID 2

Plant::Plant()
{
    //Initialize the general stuff
    textureLoaded = false;
    showTexture = true;
    killDistance = 0.1;
    initialTotalSize = 0;
    PIPE_EXP =3;
    MAX_DISTANCE = 0.4;
    tropFactor = 0;
    drawType = SKELETON;
    treeType = REGULAR;
    init_tree();
    //generateAttractionPoints();//Might be useful later on probably it own thing
}

//The deleting the contents of the plant.
Plant::~Plant(){
    delete tree;
    tree = NULL;
    attractPoints.clear();
}
//***********************************************
//Function Name: delete_tree
//
//Purpose: Deleteing the Tree Structure
//-------- Base Case:
//-------- Recursive Case:
//Input:  None
//Output: None
//
//***********************************************
void Plant::delete_tree(Branch *segment){
    //check if the node is null aka empty
    if(!isEmpty(segment)){
        //go the next segement the main branch
        delete_tree(segment->nextSegment);
        //go the lateral branch
        delete_tree(segment->lateral);
        delete segment;
        segment = NULL;
    }
}
void Plant::init_tree(){
    //Initialize the tree
     tree = new Branch();
     tree->position = Point3d(0.0, -1.0,0.0);
     tree->direction = Vector(0.0,1.0,0.0);
}

//***********************************************
//Function Name:
//
//Purpose:
//
//Input:  None
//Output: None
//
//***********************************************
void Plant::generateAttractionPoints(){
    if(treeType == REGULAR){
	generateAttractionPointsReg();
    }else{
	generateAttractionPointsPine();

    }
}

void Plant::generateAttractionPointsReg(){
    attractPoints.clear();
    Point3d centerPos(0,0.2,0); // the centre of the envolope bounding surface for now assume this is same is a sphere
    Point3d newPointPos; //The point position in the envelope
     //Randomly Create some points in a sphere
    for(int i =0; i< 1000; ++i){
	newPointPos = Point3d(0,0,0);
        do{
        newPointPos.setX(centerPos.getX() + getRand());
        newPointPos.setY(centerPos.getY() + getRand());
        newPointPos.setZ(centerPos.getZ() + getRand());
        }while((newPointPos.getDistance(centerPos)) > 0.8);

        attractPoints.push_back(newPointPos);
    }
    initialTotalSize = attractPoints.size();
    delete_tree(tree);
    init_tree();
}

void Plant::generateAttractionPointsPine(){
    attractPoints.clear();
    Point3d centerPos(0,0.2,0); // the centre of the envolope bounding surface for now assume this is same is a sphere
    Point3d circle(0,0,0);
    Point3d newPointPos; //The point position in the envelope
    //Randomly Create some points in a sphere
    for(int i =0; i< 1000; ++i){
        newPointPos = Point3d(0,0,0);
        do{
            newPointPos.setX(centerPos.getX() + getRand());
            newPointPos.setY(centerPos.getY() + getRand());
            newPointPos.setZ(centerPos.getZ() + getRand());
            circle.setY(newPointPos.getY());
        }while((newPointPos.getDistance(circle)) > (.33 - (newPointPos.getY()/3)) || (newPointPos.getDistance(centerPos)) > 1.0 );
        attractPoints.push_back(newPointPos);
    }
    initialTotalSize = attractPoints.size();
    delete_tree(tree);
    init_tree();
}
void Plant  :: update(){
    updateTreeAndTraverse(tree);
}

void Plant :: grow(){
    //For each point, go through the tree and select the closest node to it
    for(unsigned int i = 0; i< attractPoints.size(); ++i){
        //Reset the closestDistance and closestPoint values
        attractPoints[i].closestSegment = NULL;
        attractPoints[i].closestDistance = LARGE_FLOAT;

        if (treeType == REGULAR)
            traverseAndSelectClosestNode(tree,attractPoints[i]);
        else
            traverseAndSelectClosestNodePine(tree, attractPoints[i]);
        determineThresholdPoint(i);
    }
    //Grow the tree
    traverseAndGrow(tree);
    updateTreeAndTraverse(tree);
    //assignWeight(tree);
}

void Plant :: traverseAndSelectClosestNode(Branch * segment, pointASpace & aPoint){
    //if the segment next or lateral node is not null traverse to the next one
    if(segment->nextSegment != NULL){
        traverseAndSelectClosestNode(segment->nextSegment, aPoint);
    }
    if(segment->lateral != NULL)
        traverseAndSelectClosestNode(segment->lateral, aPoint);
    //else if either of the branch is null then, find the distance between the an attraction point
    //and segment's end position. Test to see if this smaller then radius of influence and the closest distance
    //Then the segement will be the attraction closest segment and distance betweem them
    if(segment->lateral == NULL || segment->nextSegment == NULL){
	double dist = segment->endPos().getDistance(aPoint.position);
	
        if (dist < MAX_DISTANCE && aPoint.closestDistance > dist)
        {
            aPoint.closestDistance = dist;
            aPoint.closestSegment = segment;
            //cout << endl;
           // cout << dist << " " <<MAX_DISTANCE << " " << aPoint.closestDistance << endl;
        }
    }
}

void Plant :: traverseAndSelectClosestNodePine(Branch * segment, pointASpace & aPoint){
    //if the segment next or lateral node is not null traverse to the next one
    if(segment->nextSegment != NULL){
        traverseAndSelectClosestNodePine(segment->nextSegment, aPoint);
    }
    if(segment->lateral != NULL)
        traverseAndSelectClosestNodePine(segment->lateral, aPoint);
    //else if either of the branch is null then, find the distance between the an attraction point
    //and segment's end position. Test to see if this smaller then radius of influence and the closest distance
    //Then the segement will be the attraction closest segment and distance betweem them
    if(segment->lateral == NULL || segment->nextSegment == NULL){
        double dist = segment->endPos().getDistance(aPoint.position);
        
        if (dist < MAX_DISTANCE && aPoint.closestDistance > dist){
            if ((aPoint.position.getY() - segment->position.getY()) < .1 && segment->position.getY() - aPoint.position.getY() < .2) {
                aPoint.closestDistance = dist;
                aPoint.closestSegment = segment;
                //cout << endl;
                // cout << dist << " " <<MAX_DISTANCE << " " << aPoint.closestDistance << endl;
            }
        }
    }
}

void Plant :: traverseAndGrow(Branch * segment){
    //if the segment next or lateral node is not null traverse to the next one
    segment->weight = 0;
    if(segment->nextSegment != NULL){
        traverseAndGrow(segment->nextSegment);
    }
    if(segment->lateral != NULL){
        traverseAndGrow(segment->lateral);
    }
    Vector growthDirection = Vector (0,0,0);
    int numPoints = 0;
    /*
    for(unsigned int i = 0; i < attractPoints.size(); ++i){
        if(attractPoints[i].closestSegment == segment){
            growthDirection += (attractPoints[i].position - segment->position).normalize();
            ++numPoints;
        }
    }
    */
    if(treeType == REGULAR) {
        for(unsigned int i = 0; i < attractPoints.size(); ++i){
            if(attractPoints[i].closestSegment == segment){
                growthDirection += (attractPoints[i].position - segment->position).normalize();
                ++numPoints;
            }
        }     }
    else if(treeType == PINE) {
        //growthDirection = segment->direction.normalize();
        for(unsigned int i = 0; i < attractPoints.size(); ++i){
            if(attractPoints[i].closestSegment == segment){
                growthDirection += (attractPoints[i].position - segment->position).normalize();
                numPoints++;
            }
            if (segment->direction.getX() == 0 && segment->direction.getY() == 1 && segment->direction.getZ() == 0) {
                if (segment->position.getY() < .7) {
                    if (segment->nextSegment == NULL){
                        growthDirection = segment->direction.normalize();
                        numPoints++;
                    }
                }
            }

        }
    }
    if (numPoints > 0)
    {
        growthDirection.unit();
        Branch * newSegment = new Branch();
        if (segment->nextSegment == NULL)
        {
	    Vector tropismV;
	    if(tropFactor > 0.0){
                Vector temp = Vector(0,1,0).cross(segment->direction.cross(Vector(0,1,0))).normalize();
		double tropAngle = M_PI / 2.0;
		tropismV = temp * sin(tropAngle) + Vector(0,1,0) * cos(tropAngle);
	    }
            newSegment->direction= ((1.0 - tropFactor)/2) * growthDirection + ((1.0 - tropFactor)/2) * segment->direction + tropFactor * tropismV;
            newSegment->position = segment->endPos();
            segment->nextSegment = newSegment;
            newSegment->lastSegment = segment;
        }
        else if (segment->lateral == NULL)
        {
            newSegment->direction = growthDirection;
            newSegment->position = segment->endPos();
            segment->lateral = newSegment;
	    newSegment->lastSegment = segment;
        }
    }
}

void Plant :: determineThresholdPoint(unsigned int & i){
        if(attractPoints[i].closestDistance < killDistance){
            //Remove the point
            attractPoints[i] = attractPoints.back();
            attractPoints.pop_back();
            --i;
        }
}
//***********************************
// Draw Some Plants here
//***********************************
void Plant :: draw(){
    glColor3d(116/255.0,64/255.0,28/255.0);
    drawTreeAndTraverse(tree);

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
    drawAttractionPoints();
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);

}
void Plant::drawAttractionPoints(){
    glColor3d(40/255.0,0.2, 240/255.0);
    glPointSize(5);
    glBegin(GL_POINTS);
    for(unsigned int i =0; i < attractPoints.size() ; ++i){
    //draw the points
        glVertex3d(attractPoints[i].position.getX(), attractPoints[i].position.getY(), attractPoints[i].position.getZ());
    }
    glEnd();
}
double Plant ::updateTreeAndTraverse(Branch * segment){
    double totalWidth = 0;
    if(segment->nextSegment != NULL){
        totalWidth += pow(updateTreeAndTraverse(segment->nextSegment),PIPE_EXP);
    }
    if(segment->lateral != NULL){
        totalWidth += pow(updateTreeAndTraverse(segment->lateral),PIPE_EXP);
    }
    double width = max(SEGMENT_WIDTH,pow(totalWidth, 1.0/PIPE_EXP));
    segment->weight = width;

    //Now draw the cylinders
    Vector normal = segment->direction.normalize();
    Point3d center = segment->position;
    segment->circle.clear();
    segment->leaves.clear();
    double radius = width * 0.0025;

    Point3d P0(1,0,0), P1(0,0,1);
    // getting two arbitrary points on a plane that has the segments direction as a normal
    if(normal.getY() != 0){// || center.getY() != 0){
        double y1 = center.getY() + ((normal.getX() *(center.getX() - P0.getX()) + normal.getZ()* (center.getZ() - P0.getZ()))/ normal.getY());
        P0.setY(y1);
        double y2 =  center.getY() + ((normal.getX() *(center.getX() - P1.getX()) + normal.getZ()* (center.getZ() - P1.getZ()))/ normal.getY());
        P1.setY(y2);
    }else{

    }
    
    // creating two vectors to be the representation of a plane that we will create a circle
    Vector V0 = (P0 - center).normalize();
    Vector V1 = (P1 - center).normalize();
    // creating and storing the points of the circle to draw as a cylinder later
    for(double i = 0.0; i < 2 * M_PI + 3; i = i +0.1){
        Point3d temp = radius * cos(i) * V0 + radius * sin(i) * V1 + center;
        segment->circle.push_back(temp);
    }
    if(width < 1.5) {
        for(double j = 0.0; j < 2 * M_PI; j = j + 0.5) {
            Vector temp = ((radius * cos(j) * V0 + radius * sin(j) * V1).normalize()) * .025;
            segment->leaves.push_back(temp);
        }
    }
    return width;
}



void Plant :: drawTreeAndTraverse(Branch * segment){

    if(segment->nextSegment != NULL){
        drawTreeAndTraverse(segment->nextSegment);
    }
    if(segment->lateral != NULL){
        drawTreeAndTraverse(segment->lateral);
    }

    if(drawType == SKELETON){
        glDisable(GL_TEXTURE_2D);
	glDisable (GL_LIGHTING);
        drawSkeletonTree(segment);
	glEnable( GL_LIGHTING );
        glEnable(GL_TEXTURE_2D);
    }else if(drawType == WIREFRAME){
        glDisable(GL_TEXTURE_2D);
	glDisable (GL_LIGHTING);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glLineWidth(1);
        drawWireFrameTree(segment);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glEnable( GL_LIGHTING );
        glEnable(GL_TEXTURE_2D);
    }else{	
        drawSmoothTree(segment);
	
    }

}
void Plant ::drawSkeletonTree(Branch *segment){
    double width = segment->weight;
    glColor3d(116/255.0,64/255.0,28/255.0);
    glLineWidth(width);
    Point3d endPosition = segment->endPos();
    glBegin(GL_LINES);
    segment->position.glv();
    endPosition.glv();
    glEnd();

    //Leaves
    glColor3f(0.0, 1.0, 0.0);
    glBegin(GL_LINES);
    if (width < 1.5) {
        if (treeType==REGULAR)
            drawLeaves(segment);
        else
            drawNeedles(segment);
    }
    glEnd();
}

void Plant ::drawWireFrameTree(Branch *segment){
    glColor3d(0.0,1.0,0.0);
    Point3d temp;
    Point3d temp2;
    glBegin(GL_TRIANGLE_STRIP);
    for(size_t i =0; i < segment->circle.size(); i++){
        temp = segment->circle[i];
        temp.glv();
        if(segment->nextSegment != NULL){
            temp2 = segment->nextSegment->circle[i];
            temp2.glv();
        }else{
            segment->endPos().glv();
        }
    }
    glEnd();
}

void Plant::drawSmoothTree(Branch *segment){
    if(textureLoaded && showTexture){
        glColor3d(1.0,1.0,1.0);
        glEnable(GL_TEXTURE_2D);
    }else{
        glDisable(GL_TEXTURE_2D);
        glColor3d(116/255.0,64/255.0,28/255.0);
    }
    Point3d temp;
    Point3d temp2;
    Vector normal = segment->direction;
    glBegin(GL_TRIANGLE_STRIP);
    for(short i =0; i < segment->circle.size(); i++){
        temp = segment->circle[i];
        glNormal3d(normal.getX(), normal.getY(), normal.getZ());
        if(textureLoaded && showTexture){
            glTexCoord2d(i/(double)(segment->circle.size()-1),0.0);
        }
        temp.glv();
        if(segment->nextSegment != NULL){
            normal = segment->nextSegment->direction;
            temp2 = segment->nextSegment->circle[i];
            if(textureLoaded && showTexture){
                 glTexCoord2d(i/(double)(segment->circle.size()-1),1.0);
            }
            glNormal3d(normal.getX(), normal.getY(), normal.getZ());
            temp2.glv();
        }else{
            if(textureLoaded && showTexture){
                 glTexCoord2d(0.5,1.0);
            }
            segment->endPos().glv();
        }
    }

    glEnd();
}

void Plant:: drawLeaves(Branch * segment) {
    Vector normal = segment->direction.normalize();
}

void Plant:: drawNeedles(Branch * segment) {
    Vector normal = segment->direction.normalize();
    Vector step = normal * .005;
    Point3d center = segment->position;
    Point3d temp;
    Point3d gravity = Point3d(0,1,0);
    gravity = gravity * .015;
    for (size_t i = 0; i < segment->leaves.size(); i++) {
        temp = center + segment->leaves[i];
        temp += (segment->direction.normalize()) * .025;
        temp += (gravity);
        center.glv();
        temp.glv();
        center = center + step;
    }
    
    
}

/*
void Plant :: drawTreeAndTraverse(Branch * segment){
    if(segment->nextSegment != NULL){
        drawTreeAndTraverse(segment->nextSegment);
    }
    if(segment->lateral != NULL){
        drawTreeAndTraverse(segment->lateral);
    }
    glLineWidth(1);
    glBegin(GL_LINES);
    glVertex3d(segment->position.getX(), segment->position.getY(),segment->position.getZ());
    glVertex3d(segment->endPos().getX(), segment->endPos().getY(),segment->endPos().getZ());//Note little optimization here
    glEnd();
}
*/
 // drawing the tree in 3d using a bunch of cylinders
void Plant :: drawTreeAndTraverse2(Branch * segment){

        // draw the tree by drawing a cylinder or something for each segment
    if(segment->nextSegment != NULL){
        drawTreeAndTraverse2(segment->nextSegment);
    }     if(segment->lateral != NULL){
        drawTreeAndTraverse2(segment->lateral);
    }
    glLineWidth(segment->weight);
    //glLineWidth(1);
    glBegin(GL_LINES);
    segment->position.glv();
    segment->endPos().glv();
    glEnd();
}

// assigns weights, or radii for all the branches
void Plant :: assignWeight(Branch * segment) {
// Set up: make segment a leaf and give it a weight of 1
    while(segment->nextSegment != NULL)
        segment = segment->nextSegment;
    segment->weight = 1.0;
    // until we get to the tree stump
    while(segment->lastSegment != NULL) {
        // if it has no lateral branch, give the last segment the same weight as this one
        if(segment->lastSegment->lateral == NULL) {
            segment->lastSegment->weight = segment->weight + .05;
            segment = segment->lastSegment;
            // if it has a lateral branch that has no weight yet, find a leaf on the lateral side,
            // give it a weight of 1 and continue from there
        } else if(segment->lastSegment->lateral->weight == 0) {
            segment = segment->lastSegment->lateral;
            while (segment->nextSegment != NULL)
                segment = segment->nextSegment;
            segment->weight = 1;
            // if it has a lateral branch with a weight, give the last segment a weight
            // of .6 of its branches.  Note:  we could change how the new weight is derived
        } else if(segment->lastSegment->lateral->weight != 0) {
            segment->lastSegment->weight = (segment->weight + segment->lastSegment->nextSegment->weight) * (PIPE_EXP * .05 + 0.5);
            segment = segment->lastSegment;
        }
    }
}


//***************************************************
//
//Function Name: loadFile
//
//Input: name- the filename to load
//Output: None
//
//Purpose: To load a valid file, and initialize the tree contents (aka attraction points)
//
//****************************************************
void Plant::loadFile(string filename){
    ifstream in;
    if(filename.length() == 0){
        return;
    }
    // remove the current points
    attractPoints.clear();

    //Try to open a file
    in.open(filename.c_str());
    if (!in.is_open()){
        cout << "Cannot open file " << filename << " for reading." << endl;
        return;
    }
    // then get the dimensions of the points
    int width=0;
    in >> width;

    // now read in the points data (and resize surface)
    attractPoints.resize(width);
    initialTotalSize = width;

    for (int i=0; i<width; i++){
        in >> attractPoints[i].position;
        //cout << temp.getX() << " " << temp.getY() << " " << temp.getZ() <<endl;
    }
    cout << "File " << filename << " read!" << endl;
    in.close();
    delete_tree(tree);
    init_tree();
}
//***************************************************
//
//Function Name: saveFile()
//
//Input: None
//Output: None
//
//Purpose: To save a file with the intials tree content,
//------- and error checks if there is a no valid points
//
//****************************************************
bool Plant::saveFile(string filename){
    // check that we have a attraction Points
    if (attractPoints.size() <= 0){
        cout << "No attraction points to save\n" << endl;
        return false;
    }else if(attractPoints.size() < (unsigned int)initialTotalSize){
	cout << "No unused attraction points to save\n" << endl;//Redo this
        return false;
    }
    ofstream out;
    out.open(filename.c_str());
    if (!out.is_open()){
        cout << "Cannot open file " << filename << " for writing." << endl;
        return false;
    }
    out << " " << attractPoints.size() << endl;
    // then write the points
    for (unsigned int i=0; i< attractPoints.size(); i++){
        out << attractPoints.at(i).position;
        out << endl;
    }
    out.close();
    cout << "File " << filename << " saved!" << endl;
    return true;
}

//***********************************************
//Function Name: isEmpty
//
//Purpose: To check if the segement node is empty
//
//Input:  None
//Output: None
//
//***********************************************
bool Plant ::isEmpty(Branch *segment){
    return(segment == NULL);
}

void Plant ::setTreeSize(double newEXP){
    PIPE_EXP = newEXP;
}

void Plant ::setRadiusOfInfl(double val){
    MAX_DISTANCE = val;
}

void Plant ::setTreeType(int Id){
    if(Id == REG_ID){
        treeType = REGULAR;
    }else if(Id == PINE_ID){
        treeType = PINE;
    }
}
void Plant ::setDrawType(int Id){
    if(Id == SKELETON_ID){
        drawType = SKELETON;
    }else if(Id == WIRE_FRAME_ID){
        drawType = WIREFRAME;
    }else{
        drawType = SMOOTH;
    }
}

double Plant ::getRadiusOfInfl()const{
   return MAX_DISTANCE;
}

void Plant::setTropismFactor(double tropCoeff){
   tropFactor = tropCoeff;
}

double Plant :: getTropismFactor()const{
   return tropFactor;
}
void Plant::setKillDistance(double val){
    killDistance = val;
}

double Plant :: getKillDistance(){
    return killDistance;
}
void Plant::initTexture(const int & height,const int & width, unsigned char * pixel){

    //generate the model's texture and bind the texture
    glGenTextures(1, &treeTexture);
    glBindTexture( GL_TEXTURE_2D, treeTexture );

    glTexImage2D(GL_TEXTURE_2D, 0, 3, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    textureLoaded = true;
}
void Plant::setShowTexture(bool check){
    showTexture = check;
}

